"""
Excel导出为Word测试文档 (Web交互式版本)
功能: 将Excel中的多级标题数据导出为Word测试文档
作者: 小花
版本: v1.2.0
更新: 支持使用Excel列字母（A、B、C）输入，更加直观易用
"""

from openpyxl import load_workbook
from openpyxl.utils.exceptions import InvalidFileException
from docx import Document
from docx.shared import Pt
from docx.opc.exceptions import PackageNotFoundError
import os
import logging
from typing import List, Dict, Optional

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)


class ExcelToWordExporter:
    """
    Excel到Word文档的导出器 (Web版本)
    
    功能：
    - 读取Excel中的多级标题数据
    - 自动添加固定的一级标题"功能测试记录"
    - 生成多级标题结构（Excel层级+1）
    - 使用Word模板保留格式
    - 自动填充父级标题路径和当前标题
    """
    
    # 常量定义
    MAX_SUPPORTED_LEVELS = 9  # 最大支持的层级数
    MIN_TABLE_ROWS = 12  # 模板表格最小行数
    MIN_TABLE_COLS = 4  # 模板表格最小列数
    FIXED_TITLE = "功能测试记录"  # 固定的一级标题
    PARENT_PATH_ROW = 1  # 父级路径填充位置（第2行，索引为1）
    CURRENT_TITLE_ROW = 3  # 当前标题填充位置（第4行，索引为3）
    FILL_COLUMN = 1  # 填充列（第2列，索引为1）

    def __init__(self):
        """初始化导出器"""
        self.max_level = 0
        self.template_doc = None
        self.template_table = None
        self.success_count = 0
        self.fail_count = 0
        logger.info("ExcelToWordExporter 初始化完成")
    
    @staticmethod
    def column_letter_to_number(column: str) -> int:
        """
        将Excel列字母转换为数字（A=1, B=2, ..., Z=26, AA=27, ...）
        
        Args:
            column: Excel列字母（如 'A', 'B', 'AA'）
            
        Returns:
            int: 列数字（从1开始）
            
        Examples:
            >>> ExcelToWordExporter.column_letter_to_number('A')
            1
            >>> ExcelToWordExporter.column_letter_to_number('Z')
            26
            >>> ExcelToWordExporter.column_letter_to_number('AA')
            27
        """
        column = column.upper().strip()
        result = 0
        for char in column:
            if not 'A' <= char <= 'Z':
                raise ValueError(f"无效的列字母: {column}")
            result = result * 26 + (ord(char) - ord('A') + 1)
        return result
    
    @staticmethod
    def column_number_to_letter(number: int) -> str:
        """
        将列数字转换为Excel列字母（1=A, 2=B, ..., 26=Z, 27=AA, ...）
        
        Args:
            number: 列数字（从1开始）
            
        Returns:
            str: Excel列字母
            
        Examples:
            >>> ExcelToWordExporter.column_number_to_letter(1)
            'A'
            >>> ExcelToWordExporter.column_number_to_letter(26)
            'Z'
            >>> ExcelToWordExporter.column_number_to_letter(27)
            'AA'
        """
        if number < 1:
            raise ValueError(f"列数字必须大于0: {number}")
        
        column = ''
        while number > 0:
            remainder = (number - 1) % 26
            column = chr(ord('A') + remainder) + column
            number = (number - 1) // 26
        return column
        
    def load_template(self, template_path: str) -> bool:
        """
        加载Word模板文档并验证
        
        Args:
            template_path: 模板文档路径
            
        Returns:
            bool: 是否加载成功
            
        Raises:
            无 - 所有异常都被捕获并返回False
        """
        try:
            # 参数验证
            if not template_path or not isinstance(template_path, str):
                logger.error("模板路径无效")
                return False
            
            # 文件存在性检查
            if not os.path.exists(template_path):
                logger.error(f"模板文件不存在: {template_path}")
                return False
            
            # 文件可读性检查
            if not os.access(template_path, os.R_OK):
                logger.error(f"模板文件无法读取: {template_path}")
                return False
                
            # 加载Word文档
            self.template_doc = Document(template_path)
            
            # 验证模板表格数量
            if len(self.template_doc.tables) != 1:
                logger.error(f"模板文档需要且只能有一张表格，当前有 {len(self.template_doc.tables)} 张")
                return False
            
            self.template_table = self.template_doc.tables[0]
            
            # 验证表格尺寸
            if len(self.template_table.rows) < self.MIN_TABLE_ROWS:
                logger.error(f"模板表格行数不足，至少需要 {self.MIN_TABLE_ROWS} 行，当前只有 {len(self.template_table.rows)} 行")
                return False
            
            if len(self.template_table.columns) < self.MIN_TABLE_COLS:
                logger.error(f"模板表格列数不足，至少需要 {self.MIN_TABLE_COLS} 列，当前只有 {len(self.template_table.columns)} 列")
                return False
            
            logger.info("✓ 模板加载成功")
            return True
            
        except PackageNotFoundError:
            logger.error(f"无法打开Word文档，文件可能损坏: {template_path}")
            return False
        except Exception as e:
            logger.error(f"加载模板失败: {type(e).__name__} - {e}")
            return False
    
    def export_to_word(self, excel_path: str, output_path: str, 
                      start_row: int, end_row: int,
                      start_col: int, end_col: int,
                      template_path: str) -> Dict[str, any]:
        """
        将Excel数据导出为Word文档
        
        Args:
            excel_path: Excel文件路径
            output_path: 输出Word文件路径
            start_row: 起始行（从1开始）
            end_row: 结束行
            start_col: 起始列（从1开始）
            end_col: 结束列
            template_path: 模板文件路径
            
        Returns:
            dict: 处理结果，包含success、message和details字段
        """
        wb = None
        try:
            # 参数验证
            validation_result = self._validate_parameters(
                excel_path, output_path, start_row, end_row, start_col, end_col
            )
            if not validation_result['valid']:
                return {
                    'success': False,
                    'message': validation_result['message']
                }
            
            # 加载模板
            if not self.load_template(template_path):
                return {
                    'success': False,
                    'message': '模板加载失败'
                }
            
            # 加载Excel文件
            logger.info(f"正在加载Excel文件: {excel_path}")
            wb = load_workbook(excel_path, data_only=True)
            ws = wb.active
            
            # 验证Excel数据范围
            if end_row > ws.max_row:
                logger.warning(f"结束行 {end_row} 超出Excel实际行数 {ws.max_row}，自动调整")
                end_row = ws.max_row
            
            if end_col > ws.max_column:
                logger.warning(f"结束列 {end_col} 超出Excel实际列数 {ws.max_column}，自动调整")
                end_col = ws.max_column
            
            # 推断层级数量
            self.max_level = min(end_col - start_col + 1, self.MAX_SUPPORTED_LEVELS)
            logger.info(f"✓ 检测到 {self.max_level} 个层级")
            
            # 保存模板表格的深拷贝，然后从模板文档中移除
            import copy
            template_table_copy = copy.deepcopy(self.template_table._element)
            self.template_doc.tables[0]._element.getparent().remove(self.template_doc.tables[0]._element)
            
            # 使用模板文档作为输出文档（保留所有样式）
            output_doc = self.template_doc
            
            # 重置计数器
            self.success_count = 0
            self.fail_count = 0
            
            # 处理数据
            logger.info(f"开始处理数据: 行{start_row}-{end_row}, 列{start_col}-{end_col}")
            self._process_excel_data(
                ws, output_doc,
                start_row, end_row,
                start_col, end_col,
                template_table_copy
            )
            
            # 保存文档
            output_dir = os.path.dirname(output_path)
            if output_dir:
                os.makedirs(output_dir, exist_ok=True)
            
            output_doc.save(output_path)
            logger.info(f"✓ 文档已保存: {output_path}")
            
            return {
                'success': True,
                'message': '导出成功',
                'details': {
                    'total_levels': self.max_level,
                    'processed_rows': end_row - start_row + 1,
                    'success_count': self.success_count,
                    'fail_count': self.fail_count,
                    'output_file': output_path
                }
            }
            
        except InvalidFileException as e:
            logger.error(f"无效的Excel文件: {e}")
            return {
                'success': False,
                'message': f'无效的Excel文件: {str(e)}'
            }
        except PermissionError as e:
            logger.error(f"文件权限错误: {e}")
            return {
                'success': False,
                'message': f'文件权限错误，无法保存: {str(e)}'
            }
        except Exception as e:
            logger.error(f"导出失败: {type(e).__name__} - {e}")
            return {
                'success': False,
                'message': f'导出失败: {str(e)}'
            }
        finally:
            # 确保Excel文件被关闭
            if wb is not None:
                try:
                    wb.close()
                    logger.debug("Excel文件已关闭")
                except Exception as e:
                    logger.warning(f"关闭Excel文件时出错: {e}")
    
    def _validate_parameters(self, excel_path: str, output_path: str,
                           start_row: int, end_row: int,
                           start_col: int, end_col: int) -> Dict[str, any]:
        """
        验证输入参数的有效性
        
        Args:
            excel_path: Excel文件路径
            output_path: 输出路径
            start_row: 起始行
            end_row: 结束行
            start_col: 起始列
            end_col: 结束列
            
        Returns:
            dict: 验证结果，包含valid和message字段
        """
        # 检查Excel路径
        if not excel_path or not isinstance(excel_path, str):
            return {'valid': False, 'message': 'Excel文件路径无效'}
        
        if not os.path.exists(excel_path):
            return {'valid': False, 'message': f'Excel文件不存在: {excel_path}'}
        
        if not os.access(excel_path, os.R_OK):
            return {'valid': False, 'message': f'Excel文件无法读取: {excel_path}'}
        
        # 检查输出路径
        if not output_path or not isinstance(output_path, str):
            return {'valid': False, 'message': '输出文件路径无效'}
        
        # 检查行列参数
        if not all(isinstance(x, int) for x in [start_row, end_row, start_col, end_col]):
            return {'valid': False, 'message': '行列参数必须为整数'}
        
        if start_row < 1 or start_col < 1:
            return {'valid': False, 'message': '起始行和起始列必须大于0'}
        
        if start_row > end_row:
            return {'valid': False, 'message': f'起始行 ({start_row}) 不能大于结束行 ({end_row})'}
        
        if start_col > end_col:
            return {'valid': False, 'message': f'起始列 ({start_col}) 不能大于结束列 ({end_col})'}
        
        if (end_col - start_col + 1) > self.MAX_SUPPORTED_LEVELS:
            return {'valid': False, 'message': f'层级数量超出限制，最多支持 {self.MAX_SUPPORTED_LEVELS} 级'}
        
        return {'valid': True, 'message': '参数验证通过'}
    
    def _process_excel_data(self, worksheet, doc, 
                           start_row: int, end_row: int,
                           start_col: int, end_col: int,
                           template_table_element):
        """
        处理Excel数据并生成Word文档
        
        Args:
            worksheet: Excel工作表对象
            doc: Word文档对象
            start_row: 起始行
            end_row: 结束行
            start_col: 起始列
            end_col: 结束列
            template_table_element: 模板表格元素
        """
        levels = self.max_level
        # 层级+2：索引0保留，索引1是固定标题"功能测试记录"，索引2开始是Excel数据
        current_titles = [""] * (levels + 2)
        
        # 添加固定的一级标题"功能测试记录"
        self._add_heading(doc, self.FIXED_TITLE, 1)
        current_titles[1] = self.FIXED_TITLE
        
        processed_rows = 0
        for row_idx in range(start_row, end_row + 1):
            last_added_level = 0
            
            # 处理每个层级（Excel层级从1开始，但在Word中从2级开始）
            for excel_lvl in range(1, levels + 1):
                col_idx = start_col + excel_lvl - 1
                cell_value = self._get_cell_value(worksheet, row_idx, col_idx)
                
                # Word中的实际层级 = Excel层级 + 1
                word_lvl = excel_lvl + 1
                
                if cell_value:
                    # 检查是否与当前层级标题不同
                    if cell_value != current_titles[word_lvl]:
                        # 更新当前层级标题
                        current_titles[word_lvl] = cell_value
                        
                        # 清空下级标题
                        for k in range(word_lvl + 1, levels + 2):
                            current_titles[k] = ""
                        
                        # 添加标题段落（使用Word层级）
                        self._add_heading(doc, cell_value, word_lvl)
                        
                        # 记录本行最后添加的层级
                        last_added_level = word_lvl
            
            # 在本行最后一个标题后插入表格
            if last_added_level > 0:
                self._insert_table(doc, current_titles, last_added_level, template_table_element)
                processed_rows += 1
        
        logger.info(f"✓ 数据处理完成: 处理了 {processed_rows} 行有效数据")
    
    def _get_cell_value(self, worksheet, row: int, col: int) -> str:
        """
        安全地获取并处理单元格值
        
        Args:
            worksheet: Excel工作表对象
            row: 行号（从1开始）
            col: 列号（从1开始）
            
        Returns:
            str: 处理后的单元格值，失败返回空字符串
        """
        try:
            cell = worksheet.cell(row, col)
            value = cell.value
            
            # 空值处理
            if value is None:
                return ""
            
            # 转换为字符串并去除首尾空格
            str_val = str(value).strip()
            
            # 过滤斜杠和None字符串（视为空值，表示继承上一行）
            if str_val in ["\\", "/", "None", ""]:
                return ""
            
            return str_val
            
        except IndexError:
            logger.warning(f"单元格索引超出范围: 行{row}, 列{col}")
            return ""
        except Exception as e:
            logger.warning(f"读取单元格失败 (行{row}, 列{col}): {e}")
            return ""
    
    def _add_heading(self, doc, text: str, level: int):
        """
        添加标题段落并设置样式
        
        Args:
            doc: Word文档对象
            text: 标题文本
            level: 标题层级（1-9）
        """
        if not text:
            logger.warning(f"尝试添加空标题，层级{level}")
            return
        
        # 防御性编程：限制层级范围
        level = max(1, min(level, 9))
        
        try:
            p = doc.add_paragraph(text)
            
            # 设置标题样式（尝试多种可能的样式名称）
            try:
                p.style = f'Heading {level}'
            except:
                try:
                    p.style = f'标题 {level}'
                except:
                    # 手动设置样式
                    if p.runs:
                        run = p.runs[0]
                    else:
                        run = p.add_run()
                    
                    # 根据层级设置字体大小
                    run.font.size = Pt(max(10, 16 - level))
                    run.font.bold = True
            
            logger.debug(f"添加{level}级标题: {text}")
            
        except Exception as e:
            logger.error(f"添加标题失败: {text}, 层级{level}, 错误: {e}")
    
    def _insert_table(self, doc, title_path: List[str], level: int, template_table_element):
        """
        在标题后插入表格并填充内容
        
        Args:
            doc: Word文档对象
            title_path: 标题路径列表
            level: 当前层级
            template_table_element: 模板表格的XML元素
        """
        current_title = title_path[level] if level < len(title_path) else ""
        
        if not current_title:
            logger.warning(f"标题路径索引{level}为空，跳过插入表格")
            self.fail_count += 1
            return
        
        try:
            from docx.oxml import parse_xml
            from lxml import etree
            
            # 克隆模板表格（保留命名空间）
            xml_str = etree.tostring(template_table_element, encoding='unicode')
            new_table_element = parse_xml(xml_str)
            
            # 插入到文档
            if len(doc.paragraphs) > 0:
                last_para = doc.paragraphs[-1]._element
                last_para.addnext(new_table_element)
            else:
                doc._element.body.append(new_table_element)
            
            # 获取刚插入的表格对象
            if len(doc.tables) == 0:
                raise RuntimeError("表格插入失败，文档中没有表格")
            
            table = doc.tables[-1]
            
            # 构建父级标题路径（从索引2开始到level-1，不包含"功能测试记录"）
            if level > 2:
                # 如果层级大于2（即至少是3级标题），包含Excel中的上级标题
                parent_path = "-".join([
                    title_path[i] for i in range(2, level)
                    if i < len(title_path) and title_path[i]
                ])
            else:
                # 如果是2级标题，没有Excel中的上级
                parent_path = "(无上级)"
            
            # 填充表格内容
            if len(table.rows) >= self.MIN_TABLE_ROWS and len(table.columns) >= self.MIN_TABLE_COLS:
                try:
                    # 填充第2行第2列: 父级标题路径
                    cell_2_2 = table.rows[self.PARENT_PATH_ROW].cells[self.FILL_COLUMN]
                    self._fill_cell(cell_2_2, parent_path)
                    
                    # 填充第4行第2列: 当前标题
                    cell_4_2 = table.rows[self.CURRENT_TITLE_ROW].cells[self.FILL_COLUMN]
                    self._fill_cell(cell_4_2, current_title)
                    
                except IndexError as e:
                    logger.error(f"表格单元格索引错误: {e}")
                    self.fail_count += 1
                    return
            else:
                logger.warning(f"表格尺寸不足，无法填充内容")
            
            self.success_count += 1
            logger.debug(f"✓ 插入表格: {current_title}")
            
        except Exception as e:
            self.fail_count += 1
            logger.error(f"✗ 插入表格失败: {current_title} - {type(e).__name__}: {e}")
        
        # 添加空行
        try:
            doc.add_paragraph()
        except Exception as e:
            logger.warning(f"添加空行失败: {e}")
    
    def _fill_cell(self, cell, text: str):
        """
        填充单元格文本，保持原有格式
        
        Args:
            cell: 单元格对象
            text: 要填充的文本
        """
        try:
            # 清空所有段落
            for para in cell.paragraphs:
                para.clear()
            
            # 在第一个段落中添加文本
            if cell.paragraphs:
                cell.paragraphs[0].add_run(text)
            else:
                # 如果没有段落，使用简单方式
                cell.text = text
                
        except Exception as e:
            logger.warning(f"填充单元格失败: {e}, 使用fallback方式")
            try:
                cell.text = text
            except:
                pass
    
    def get_excel_info(self, excel_path: str) -> Dict[str, any]:
        """
        获取Excel文件信息
        
        Args:
            excel_path: Excel文件路径
            
        Returns:
            dict: 文件信息，包含success、sheet_name、max_row、max_column等字段
        """
        wb = None
        try:
            # 参数验证
            if not excel_path or not isinstance(excel_path, str):
                return {
                    'success': False,
                    'message': 'Excel文件路径无效'
                }
            
            if not os.path.exists(excel_path):
                return {
                    'success': False,
                    'message': f'Excel文件不存在: {excel_path}'
                }
            
            if not os.access(excel_path, os.R_OK):
                return {
                    'success': False,
                    'message': f'Excel文件无法读取: {excel_path}'
                }
            
            # 加载Excel文件
            logger.info(f"读取Excel文件信息: {excel_path}")
            wb = load_workbook(excel_path, data_only=True, read_only=True)
            ws = wb.active
            
            # 收集信息
            info = {
                'success': True,
                'sheet_name': ws.title or '未命名',
                'max_row': ws.max_row or 0,
                'max_column': ws.max_column or 0,
                'sheets': wb.sheetnames or [],
                'file_path': excel_path
            }
            
            logger.info(f"✓ Excel信息: {ws.title}, {info['max_row']}行 x {info['max_column']}列")
            return info
            
        except InvalidFileException as e:
            logger.error(f"无效的Excel文件: {e}")
            return {
                'success': False,
                'message': f'无效的Excel文件格式: {str(e)}'
            }
        except PermissionError as e:
            logger.error(f"文件权限错误: {e}")
            return {
                'success': False,
                'message': f'文件权限错误，无法访问: {str(e)}'
            }
        except Exception as e:
            logger.error(f"读取Excel失败: {type(e).__name__} - {e}")
            return {
                'success': False,
                'message': f'读取Excel失败: {str(e)}'
            }
        finally:
            # 确保文件被关闭
            if wb is not None:
                try:
                    wb.close()
                    logger.debug("Excel文件已关闭")
                except Exception as e:
                    logger.warning(f"关闭Excel文件时出错: {e}")
